﻿// using System.IO;
using System.Buffers.Text;
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading.Tasks;
using UMC.SshNet;
using UMC.Net;

namespace UMC.ITME
{

    public abstract class HttpMime : IDisposable
    {

        public abstract int Id { get; }
        public int ActiveTime
        {

            get; set;
        }
        public int TimeOut { protected set; get; } = 20;

        public virtual string Scheme => "http";


        public void OutputFinish()
        {

            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            this.TimeOut = 20;
            this.Request = null;

        }
        public abstract void Write(byte[] buffer, int offset, int count);


        public abstract void Dispose();
        public abstract String Host { get; }
        public abstract String RemoteIpAddress { get; }


        protected MimeRequest Request;

        protected void WebSocket(NetContext context)
        {
            if (context.Tag is NetHttpRequest)
            {
                var webr = context.Tag as NetHttpRequest;
                this.WebSocket(webr);
            }
            else
            {
                OutText(403, "WebSocket");
            }
        }

        void WebSocket(NetHttpRequest webRequest)
        {
            var url = webRequest.Address;
            webRequest.Headers["Connection"] = "Upgrade";

            var eventArgs = new ConnectAsyncEventArgs
            {
                Connected = async (client) =>
                {

                    byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);

                    try
                    {
                        if (url.Scheme == "https")
                        {
                            SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
                            await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);
                            var textWriter = new TextWriter(ssl.Write, _data);
                            // textWriter
                            UMC.Net.NetHttpResponse.Header(webRequest, textWriter);

                            textWriter.Dispose();

                            int size = await ssl.ReadAsync(_data, 0, _data.Length);

                            if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                            {
                                this.Request = new HttpsWebSocket(this, ssl);

                                this.Write(_data, 0, size);
                            }
                            else
                            {
                                this.Write(_data, 0, size);
                                this.Dispose();
                            }
                        }
                        else
                        {
                            var textWriter = new TextWriter((b, c, s) =>
                            {
                                client.Send(b, c, s, SocketFlags.None);
                            }, _data);
                            UMC.Net.NetHttpResponse.Header(webRequest, textWriter);
                            textWriter.Dispose();

                            var size = client.Receive(_data, 0, _data.Length, SocketFlags.None);
                            if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                            {
                                this.Request = new HttpWebSocket(this, client);
                                this.Write(_data, 0, size);
                            }
                            else
                            {

                                this.Write(_data, 0, size);
                                this.Dispose();
                            }

                        }
                    }
                    catch (Exception ex)
                    {
                        this.OutText(500, ex.ToString());
                    }
                    finally
                    {
                        System.Buffers.ArrayPool<byte>.Shared.Return(_data);
                    }
                },


                Error = (ex) =>
                {
                    this.OutText(500, ex.ToString());
                }
            };
            eventArgs.Start(0, url.Host, url.Port);


            HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
        }
        void SSH(SshClient sshClient, UMC.Host.HttpWebSocket webSocket, Entities.Device device, NameValueCollection query, String username)
        {
            sshClient.Connect();
            const string terminalName = "vt100";
            uint columns = Utility.Parse(query.Get("c"), 80u);
            uint rows = Utility.Parse(query.Get("r"), 25u);
            uint width = Utility.Parse(query.Get("w"), 640u);
            uint height = Utility.Parse(query.Get("h"), 480u);
            const int bufferSize = 100;
            var inputIndex = 0;
            var count = 0;
            var input = new char[200];
            var isPwd = false;
            var isInput = false;
            var strea = sshClient.CreateShellStream(terminalName, columns, rows, width, height, bufferSize);
            webSocket.Close = () =>
            {
                sshClient.Disconnect();
                strea.Dispose();
            };

            webSocket.Send("{\"type\":\"info\",\"value\":{\"Title\":\"" + device.Caption + "\",\"Key\":\"" + device.Id + "\"}}");
            strea.DataReceived += (b, e) =>
            {
                var lines = System.Text.Encoding.UTF8.GetString(e.Data).Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
                if (lines.Length > 0)
                {
                    var str = lines[lines.Length - 1];

                    if (isInput)
                    {
                        if (lines.Length == 1)
                        {
                            var IsTo = false;
                            var ToValue = -1;
                            for (var i = 0; i < str.Length; i++)
                            {
                                var by = str[i];
                                switch (by)
                                {
                                    case '\a':
                                        break;
                                    case '\b':
                                        inputIndex--;
                                        break;
                                    case '\u001b':
                                        if (i + 1 < str.Length)
                                        {
                                            if (str[i + 1] == '[')
                                            {
                                                ToValue = i + 2;
                                                i++;
                                                IsTo = true;
                                            }
                                        }
                                        break;
                                    default:
                                        if (IsTo)
                                        {
                                            if ((by >= 'a' && by <= 'z') || (by >= 'A' && by <= 'Z'))
                                            {
                                                var tv = ToValue == i ? "1" : str.Substring(ToValue, i - ToValue);
                                                switch (by)
                                                {
                                                    case 'K':
                                                        count = inputIndex;
                                                        break;
                                                    case 'C':
                                                        inputIndex += Utility.IntParse(tv, 1);
                                                        break;
                                                    case 'D':
                                                        inputIndex -= Utility.IntParse(tv, 1);
                                                        break;
                                                }
                                                IsTo = false;
                                            }
                                        }
                                        else if (input.Length > inputIndex)
                                        {
                                            input[inputIndex] = by;
                                            inputIndex++;
                                            count = inputIndex;
                                        }
                                        break;
                                }
                            }
                        }
                    }
                    else
                    {
                        if (str[0] == '[')
                        {
                            isInput = device.Username.StartsWith(str, 1, device.Username.Length);
                        }
                        else
                        {
                            isInput = str.StartsWith(device.Username);
                        }
                        if (isPwd && isInput)
                        {
                            if (lines.Length == 2)
                            {
                                if (lines[0].StartsWith("/"))
                                {
                                    webSocket.Send("{\"type\":\"view\",\"value\":{\"Path\":\"" + lines[0] + "\",\"Key\":\"" + device.Id + "\"}}");

                                }

                            }
                        }
                    }
                }
                webSocket.Send(e.Data, 0, e.Data.Length);

            };
            webSocket.ReceiveData = (b, c, l) =>
            {
                if (b[c] == 13)
                {
                    isInput = false;
                    if (count > 0)
                    {
                        var ls = new String(input.AsSpan(0, count));

                        isPwd = ls == "pwd";
                        inputIndex = 0;
                        count = 0;
                        Activities.SiteSSHActivities.SSHLog(device, username, ls);
                    }
                }
                try
                {
                    strea.Write(b, c, l);
                    strea.Flush();
                }
                catch
                {
                    webSocket.Disconnect();
                }
            };
            strea.ErrorOccurred += (e, b) =>
            {
                sshClient.Disconnect();
                strea.Dispose();
            };
        }
        public virtual void PrepareRespone(HttpMimeRequest request)
        {

            this.TimeOut = 300;
            try
            {
                if (request.IsWebSocket)
                {
                    if (request.RawUrl.StartsWith("/UMC.WS/"))
                    {
                        var Path = request.Url.AbsolutePath.Substring(8);
                        
                        var ds = request.Cookies.GetValues(WebServlet.SessionCookieName) ?? new string[] { Path };
                        if (ds.Length > 0)
                        {
                            string secWebSocketKey = request.Headers["Sec-WebSocket-Key"];
                            if (String.IsNullOrEmpty(secWebSocketKey) == false)
                            {
                                var buffers = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
                                Guid dID;

                                if (ds.Any(r => r == Path))
                                {
                                    ds[0] = Path;
                                    dID = UMC.Data.Utility.Guid(Path, true).Value;
                                }
                                else
                                {
                                    dID = SiteConfig.MD5Key(String.Join(',', ds), Path);
                                }
                                var webr = new UMC.Host.HttpWebSocket(this.Write, dID, this.Dispose);
                                this.Request = webr;

                                var size = secWebSocketKey.WriteBytes(buffers, 0);
                                size += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".WriteBytes(buffers, size);
                                int len = System.Security.Cryptography.SHA1.HashData(buffers.AsSpan(0, size), buffers.AsSpan(size, 24));
                                string secWebSocketAcceptString = Convert.ToBase64String(buffers.AsSpan(size, len));
                                var writer = new Net.TextWriter(request._context.Write, buffers);
                                writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
                                writer.Write("Connection: Upgrade\r\n");
                                writer.Write("Upgrade: websocket\r\n");
                                writer.Write($"Sec-WebSocket-Accept: {secWebSocketAcceptString}\r\n");
                                writer.Write("Server: ITME\r\n\r\n");
                                writer.Flush();
                                writer.Dispose();
                                System.Buffers.ArrayPool<byte>.Shared.Return(buffers);
                                HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
                                var devices = Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
                                if (devices.Length > 1)
                                {
                                    var deviceId = Data.Utility.Guid(ds[0], true).Value;
                                    var session = new Data.Session<Data.AccessToken>(deviceId.ToString());
                                    if (session.Value != null && session.Value.Device == deviceId)
                                    {
                                        if (session.Value.IsInRole(UMC.Security.Membership.UserRole))
                                        {
                                            if (Security.AuthManager.Authorization(session.Value.Identity(), 0, $"WebSSH/{devices[1]}", out var isBiometric) > 0)
                                            {
                                                if (isBiometric && session.Value.BiometricTime == 0)
                                                {
                                                    var seesionKey = UMC.Data.Utility.Guid(session.Value.Device.Value);
                                                    var url = $"/Biometric?oauth_callback={Uri.EscapeDataString(request.Url.AbsoluteUri)}&transfer={seesionKey}";
                                                    webr.Send("{\"type\":\"url\",\"value\":" + url + "\"}}");
                                                }
                                                else
                                                {
                                                    var device = UMC.Data.HotCache.Get(new Entities.Device { Id = Utility.IntParse(devices[1], 0) });
                                                    if (device != null)
                                                    {

                                                        var us = UMC.Data.License.GetLicense("WebSSH", 5);
                                                        Data.Caches.ICacheSet cacheSet2 = UMC.Data.HotCache.Cache<Entities.Device>();
                                                        if ((cacheSet2.Count > us.Quantity && us.Quantity > 0) || (us.ExpireTime > 0 && us.ExpireTime < Utility.TimeSpan()))
                                                        {
                                                            webr.Send("{\"type\":\"license\",\"msg\":\"设备数量超限,请保持合规\"}");
                                                            // return;
                                                            webr.Disconnect();
                                                            return;
                                                        }
                                                        var password = UMC.Data.DataFactory.Instance().Password(SiteConfig.MD5Key(device.Ip, device.Username));

                                                        var ssh = new SshClient(device.Ip, device.Port ?? 22, device.Username, password);

                                                        var _QueryString = System.Web.HttpUtility.ParseQueryString(request.Url.Query);
                                                        webr.Send("{\"type\":\"device\",\"value\":\"" + Utility.Guid(dID) + "\"}");

                                                        this.SSH(ssh, webr, device, _QueryString, session.Value.Username);
                                                        return;

                                                    }
                                                }
                                            }
                                        }

                                    }
                                }
                                var lic = UMC.Data.License.GetLicense("UserSession", 1000);

                                Data.Caches.ICacheSet cacheSet = UMC.Data.HotCache.Cache<UMC.Data.Entities.Session>();
                                if ((cacheSet.Count > lic.Quantity && lic.Quantity > 0) || (lic.ExpireTime > 0 && lic.ExpireTime < Utility.TimeSpan()))
                                {
                                    webr.Send("{\"msg\":\"会话规模超限,请保持合规\"}");
                                }
                            }
                            else
                            {
                                OutText(403, "not validate websocket headers");
                            }
                        }
                        else
                        {
                            OutText(403, "Permission denied");
                        }
                    }
                    else
                    {
                        var context = new HttpMimeContext(request, new HttpMimeResponse(this, request));
                        context.ProcessRequest();
                        this.WebSocket(context);
                    }
                }
                else
                {
                    var context = new HttpMimeContext(request, new HttpMimeResponse(this, request));
                    context.ProcessRequest();
                    context.ProcessAfter();
                }

            }
            catch (Exception ex)
            {
                OutText(500, "text/plain", ex.ToString());

            }


        }

        public void OutText(int status, string contentType, String text)
        {
            var writer = new TextWriter(this.Write);
            writer.Write($"HTTP/1.1 {status} {HttpStatusDescription.Get(status)}\r\n");
            writer.Write($"Content-Type: {contentType}; charset=utf-8\r\n");
            writer.Write($"Content-Length: {System.Text.Encoding.UTF8.GetByteCount(text)}\r\n");
            writer.Write("Connection: close\r\n");
            writer.Write("Server: ITME\r\n\r\n");
            writer.Write(text);
            writer.Flush();
            writer.Close();
            this.Dispose();
        }
        public void OutText(int status, String text)
        {
            this.OutText(status, "text/plain", text);

        }
    }
}

